﻿Imports LiarsDice.Utilities

Public Class GameEngine
    Implements IDisposable

    Private _players As New List(Of PlayerBase)()

    Public Const MaxPlayers As Integer = 4

    Public Event PlayerJoined(ByVal player As PlayerBase)
    Public Event NewRoundStarting(ByVal round As Integer)
    Public Event GameHasEnded()
    Public Event BidMade(ByVal player As PlayerBase, ByVal bid As Bid)
    Public Event GameStarted(ByVal game As GameEngine)
    Public Event RoundOver(ByVal loser As PlayerBase)

    Private _round As Integer = 0
    Private _startingPlayerIndex As Integer = -1
    Private _nextToMakeBidIndex As Integer
    Private _gameState As GameState

    Public Sub New()
        InitializeEngine()
    End Sub

    Private Sub InitializeEngine()
        Me._gameState = GameState.WaitingForPlayers
    End Sub

    ''' <summary>
    ''' Selects a starting player at random and rolls the dices for everyone.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Start()
        ' Only allow the game to start if it isn't already ongoing
        If (Me.State = GameState.WaitingForPlayers Or Me.State = GameState.GameOver) Then
            ' Let everyone know that the game has now started
            Me.State = GameState.GameStarted
            RaiseEvent GameStarted(Me)

            ' Start a new round
            NewRound()

            ' Notify the first player that we want him to bid
            _players(_nextToMakeBidIndex).AskForBid(Nothing)
        End If
    End Sub

    ''' <summary>
    ''' Generates a specific amount of random dice values.
    ''' </summary>
    ''' <param name="amount">The amount of values to generate</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function GenerateDices(ByVal amount As Integer) As IEnumerable(Of Integer)
        Dim dices As New List(Of Integer)

        For index = 1 To amount
            dices.Add(RandomGenerator.Instance.Next(1, 7))
        Next
        Return dices
    End Function

    ''' <summary>
    ''' Starts a new round
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub NewRound()
        ' if this is the first round, then assign a random player to go first
        If (IsFirstRound()) Then
            SelectRandomPlayerToStart()
        Else ' else it should be either the loser or next logical player to start
            AdvanceStartingPlayer()
        End If

        ' increase round count
        Round += 1

        ' Roll dices for everyone
        For Each p In _players
            p.HiddenDices.AddRange(GenerateDices(NumberOfDicesPerPlayer))
        Next

        RaiseEvent NewRoundStarting(Round)
    End Sub

    ''' <summary>
    ''' Selects the starting player for the new round
    ''' </summary>
    ''' <remarks>Right now the next starting player is the one next to the previous. We might want to change this to the loser of last round?</remarks>
    Private Sub AdvanceStartingPlayer()
        _startingPlayerIndex = (_startingPlayerIndex + 1) Mod PlayerCount
        _nextToMakeBidIndex = _startingPlayerIndex
    End Sub

    ''' <summary>
    ''' Selects a starting player for a new game. 
    ''' </summary>
    ''' <remarks>This is currently done randomly.</remarks>
    Private Sub SelectRandomPlayerToStart()
        _startingPlayerIndex = RandomGenerator.Instance.Next(0, _players.Count)
        _nextToMakeBidIndex = _startingPlayerIndex
    End Sub

    ''' <summary>
    ''' Determines whether no rounds have been played yet.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function IsFirstRound() As Boolean
        Return _startingPlayerIndex = -1
    End Function

    ''' <summary>
    ''' This function is called when a player makes a bid from the PlayerBase.MakeBid method.
    ''' </summary>
    ''' <param name="player"></param>
    ''' <param name="bid"></param>
    ''' <remarks></remarks>
    Private Sub PlayerMadeABid(ByVal player As PlayerBase, ByVal bid As Bid)
        ' Verify that the player who gave this bid actually is in turn
        If (GetIndexForPlayer(player) = Me._nextToMakeBidIndex) Then



            ' Announce that a bid was made
            RaiseEvent BidMade(player, bid)

            AdvancePlayerToMakeBid()

            ' Ask the next player for a bid
            _players(_nextToMakeBidIndex).AskForBid(bid)
        End If
    End Sub

    ''' <summary>
    ''' Select the next player to make a bid
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub AdvancePlayerToMakeBid()
        _nextToMakeBidIndex = (_nextToMakeBidIndex + 1) Mod PlayerCount
    End Sub

    ''' <summary>
    ''' Gets the index of a specific player according to the player list.
    ''' </summary>
    ''' <param name="player"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function GetIndexForPlayer(ByVal player As PlayerBase) As Integer
        Return _players.IndexOf(player)
    End Function

    ''' <summary>
    ''' Adds a player to the game. The player can be either an AI or human.
    ''' </summary>
    ''' <param name="participant"></param>
    ''' <returns>True if the player was succesfully added. False if the game was already started or the game is full</returns>
    ''' <remarks></remarks>
    Public Function AddPlayer(ByVal participant As PlayerBase) As Boolean
        ' Ensure that the game hasn't started
        If (Me.State = GameState.WaitingForPlayers) Then

            ' Ensure that there is still room in the game
            If (_players.Count >= MaxPlayers) Then
                Return False
            End If

            _players.Add(participant)

            ' Listen to when the player makes a bid
            AddHandler participant.BidMade, AddressOf PlayerMadeABid

            ' Tell everyone that a player has joined
            RaiseEvent PlayerJoined(participant)
            Return True
        End If

        Return False
    End Function

#Region "-Properties-"

    Public Property State() As GameState
        Get
            Return _gameState
        End Get
        Private Set(ByVal value As GameState)
            _gameState = value
        End Set
    End Property


    Public Property Round() As Integer

        Get
            Return _round
        End Get
        Private Set(ByVal value As Integer)
            _round = value
        End Set
    End Property


    Public ReadOnly Property PlayerCount() As Integer
        Get
            Return _players.Count
        End Get
    End Property

    Public ReadOnly Property NumberOfDicesPerPlayer() As Integer
        Get
            Return 5
        End Get
    End Property

#End Region

    Public Sub Dispose() Implements IDisposable.Dispose
        RaiseEvent GameHasEnded()
    End Sub
End Class